﻿using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace App_Code
{
    [Flags]
    public enum BOARD_VALUE : byte
    {
        ZERO = 0,
        ONE = 1,
        TWO = 2,
        THREE = 3,
        FOUR = 4,
        FIVE = 5,
        SIX = 6,
        SEVEN = 7,
        EIGHT = 8,
        MINE = 9,

        NUMBER = 15,

        IS_OPENED = 16,
        IS_FLAG = 32,
        IS_QUESTION = 64

    }

    public static class BoardValueExtender
    {
        public static byte GetValue(this BOARD_VALUE v)
        {
            return (byte)(v & BOARD_VALUE.NUMBER);
        }

        public static bool IsMine(this BOARD_VALUE v)
        {
            return v.GetValue() == 9;
        }

        public static bool IsFlag(this BOARD_VALUE v)
        {
            return (v & BOARD_VALUE.IS_FLAG) > 0;
        }
    
        public static bool IsOpened(this BOARD_VALUE v)
        {
            return (v & BOARD_VALUE.IS_OPENED) > 0;
        }
    }

    public class MineBoard
    {
        public int XSize { get; private set; }
        public int YSize { get; private set; }
        public int MineCount { get; private set; }

        public BOARD_VALUE this[int x, int y]
        {
            get
            {
                return _Board[x, y];
            }
        }

        public MineBoard(int xSize, int ySize, int mineCount)
        {
            if (xSize <= 0)
                throw new ArgumentException("Invalid xSize", "xSize");
            if (ySize <= 0)
                throw new ArgumentException("Invalid ySize", "ySize");

            if (xSize * ySize < mineCount)
            {
                throw new InvalidOperationException("Invalid board parameters. Too many mines");
            }

            XSize = xSize;
            YSize = ySize;
            MineCount = mineCount;

            _Board = new BOARD_VALUE[xSize, ySize];

            var mc = mineCount;
            var r = new Random();
            while (mc > 0)
            {
                var x = r.Next(xSize);
                var y = r.Next(ySize);
                if (_Board[x, y] == 0)
                {
                    _Board[x, y] = BOARD_VALUE.MINE;
                    --mc;
                }
            }

            for (int i = 0; i < xSize; i++)
            {
                for (int j = 0; j < ySize; j++)
                {
                    byte count = 0;
                    Action<int, int, BOARD_VALUE> calcCount = (x, y, v) => { if (v == BOARD_VALUE.MINE) count++; };
                    Around(i, j, calcCount);
                    _Board[i, j] = (BOARD_VALUE)count;
                }
            }
        }

        public MineBoard(int xSize, int ySize, int mineCount, byte[] storeArray)
        {
            XSize = xSize;
            YSize = ySize;
            MineCount = mineCount;
            var ind = 0;
            for (int i = 0; i < xSize; i++)
                for (int j = 0; j < ySize; j++)
                {
                    var v = (BOARD_VALUE) storeArray[ind++];
                    _Board[i, j] = v;
                }
        }

        public string ToStoreString()
        {
            var b = new StringBuilder((XSize + 2) * YSize + 15);

            b.AppendLine(XSize.ToString());
            b.AppendLine(YSize.ToString());
            for (int i = 0; i < XSize; i++)
            {
                for (int j = 0; j < YSize; j++)
                    b.Append((byte) _Board[i, j]);

                if (i != XSize)
                    b.AppendLine();
            }

            return b.ToString();
        }

        public byte[] ToByteArray()
        {
            var res = new byte[XSize * YSize];
            int ind = 0;
            for (int i = 0; i < XSize; i++)
                for (int j = 0; j < YSize; j++)
                    res[ind++] = (byte)_Board[i, j];
            return res;
        }

        public int Open(int x, int y, Action<int, int> openCell)
        {
            if (this[x, y].IsMine())
                return 2;

            openCell(x, y);
            if (this[x, y].GetValue() == 0)
                Around(x, y, (ix, iy, v) => Open(ix, iy, openCell));
            return 0; // TODO: Implement victory calculation
        }

        private void Around(int x, int y, Action<int, int, BOARD_VALUE> action)
        {
            for (int i = -1; i <= 1; ++i)
            {
                for (int j = -1; j <= 1; ++j)
                {
                    var nx = x + i;
                    var ny = y + j;
                    if ((i != 0 || j != 0) && InBound(nx, ny))
                    {
                        action(nx, ny, _Board[nx, ny]);
                    }
                }
            }
        }

        private bool InBound(int x, int y)
        {
            return 0 <= x && x < XSize && 0 <= y && y < YSize;
        }

        private readonly BOARD_VALUE[,] _Board;
        private int _OpenedMineCount;
    }
}
